/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/**
 * @version func.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 * 
 * Runner functions
 */

#include "bspd.h"

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include <stdlib.h>
#include <string.h>

/* Register */
int func_regist(lua_State *l)
{
	// Regist C functions
	lua_pushcfunction(l, func_l_set_group);
	lua_setglobal(l, "bsp_set_group");
	
	lua_pushcfunction(l, func_l_set_room);
	lua_setglobal(l, "bsp_set_room");
	
	lua_pushcfunction(l, func_l_set_uid);
	lua_setglobal(l, "bsp_set_uid");
	
	lua_pushcfunction(l, func_l_set_uname);
	lua_setglobal(l, "bsp_set_uname");
	
	lua_pushcfunction(l, func_l_set_extends);
	lua_setglobal(l, "bsp_set_extends");
	
	lua_pushcfunction(l, func_l_get_group);
	lua_setglobal(l, "bsp_get_group");
	
	lua_pushcfunction(l, func_l_get_room);
	lua_setglobal(l, "bsp_get_room");
	
	lua_pushcfunction(l, func_l_get_uid);
	lua_setglobal(l, "bsp_get_uid");
	
	lua_pushcfunction(l, func_l_get_uname);
	lua_setglobal(l, "bsp_get_uname");
	
	lua_pushcfunction(l, func_l_get_extends);
	lua_setglobal(l, "bsp_get_extends");

	lua_pushcfunction(l, func_l_room_add);
	lua_setglobal(l, "bsp_room_add");
	
	lua_pushcfunction(l, func_l_group_add);
	lua_setglobal(l, "bsp_group_add");
	
	lua_pushcfunction(l, func_l_group_del);
	lua_setglobal(l, "bsp_group_del");
	
	lua_pushcfunction(l, func_l_list_group_client);
	lua_setglobal(l, "bsp_group_client_list");

	lua_pushcfunction(l, func_l_get_group_runner);
	lua_setglobal(l, "bsp_get_group_runner");

	lua_pushcfunction(l, func_l_get_group_room);
	lua_setglobal(l, "bsp_get_group_room");
	
	lua_pushcfunction(l, func_l_set_group_extends);
	lua_setglobal(l, "bsp_set_group_extends");
	
	lua_pushcfunction(l, func_l_get_group_extends);
	lua_setglobal(l, "bsp_get_group_extends");

	lua_pushcfunction(l, func_l_coin_trans);
	lua_setglobal(l, "bsp_coin_trans");
	
	lua_pushcfunction(l, func_l_event_add);
	lua_setglobal(l, "bsp_event_add");
	
	lua_pushcfunction(l, func_l_event_del);
	lua_setglobal(l, "bsp_event_del");
	
	lua_pushcfunction(l, func_l_net_send);
	lua_setglobal(l, "bsp_net_send");
	
	lua_pushcfunction(l, func_l_net_close);
	lua_setglobal(l, "bsp_net_close");
	
	lua_pushcfunction(l, func_l_mysql_db);
	lua_setglobal(l, "bsp_mysql_db");
	
	lua_pushcfunction(l, func_l_mysql_query);
	lua_setglobal(l, "bsp_mysql_query");
	
	lua_pushcfunction(l, func_l_mysql_fetch_row);
	lua_setglobal(l, "bsp_mysql_fetch_row");

	lua_pushcfunction(l, func_l_list_client);
	lua_setglobal(l, "bsp_list_client");

	lua_pushcfunction(l, func_l_list_group);
	lua_setglobal(l, "bsp_list_group");
	
	return RTN_SUCCESS;
}

// ================ Func list ================
/* Set client's current group and room */
int func_l_set_group(lua_State *l)
{
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	mempool_set_client_group(client_id, group_id);
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	
	return 1;
}

int func_l_set_room(lua_State *l)
{
	int room_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	mempool_set_client_room(client_id, room_id);
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	
	return 1;
}

/* Set username and uid */
int func_l_set_uid(lua_State *l)
{
	unsigned long long user_id = (unsigned long long) lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	mempool_set_client_uid(client_id, user_id);
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	
	return 1;
}

int func_l_set_uname(lua_State *l)
{
	char *username = strdup(lua_tostring(l, -1));
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	mempool_set_client_uname(client_id, username);
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	free(username);
	
	return 1;
}

/* Set extend data */
int func_l_set_extends(lua_State *l)
{
	unsigned long long data = (unsigned long long) lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int index = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	mempool_set_client_extends(client_id, index, data);
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	
	return 1;
}

/* Get client's current group and room */
int func_l_get_group(lua_State *l)
{
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int group_id = mempool_get_client_group(client_id);
	
	runner_clear_stack(l);
	lua_pushinteger(l, group_id);
	
	return 1;
}

int func_l_get_room(lua_State *l)
{
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int room_id = mempool_get_client_room(client_id);
	
	runner_clear_stack(l);
	lua_pushinteger(l, room_id);
	
	return 1;
}

/* Get username and uid */
int func_l_get_uid(lua_State *l)
{
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	unsigned long long user_id = mempool_get_client_uid(client_id);
	
	lua_pushinteger(l, user_id);
	
	return 1;
}

int func_l_get_uname(lua_State *l)
{
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	char *username = mempool_get_client_uname(client_id);
	
	lua_pushstring(l, username);
	
	return 1;
}

/* Get extend data */
int func_l_get_extends(lua_State *l)
{
	int index = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	unsigned long long data = mempool_get_client_extends(client_id, index);
	
	runner_clear_stack(l);
	lua_pushinteger(l, data);
	
	return 1;
}

/* Add a room */
int func_l_room_add(lua_State *l)
{
	char *room_name = strdup(lua_tostring(l, -1));
	lua_remove(l, -1);

	int room_id = mempool_add_room(room_name);
	lua_getglobal(l, "runner_id");
	int runner_id = lua_tointeger(l, -1);
	mempool_set_room_runner(room_id, runner_id);

	runner_clear_stack(l);
	free(room_name);
	lua_pushinteger(l, room_id);
	
	return 1;
}

/* Add a group */
int func_l_group_add(lua_State *l)
{
	char *group_password = strdup(lua_tostring(l, -1));
	lua_remove(l, -1);
	
	char *group_name = strdup(lua_tostring(l, -1));
	lua_remove(l, -1);

	int room_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int group_id = mempool_add_group(group_name, group_password);
	lua_getglobal(l, "runner_id");
	int runner_id = lua_tointeger(l, -1);

	mempool_set_group_runner(group_id, runner_id);
	mempool_set_group_room(group_id, room_id);
	
	runner_clear_stack(l);
	free(group_name);
	free(group_password);
	lua_pushinteger(l, group_id);
	
	return 1;
}

/* Remove a group */
int func_l_group_del(lua_State *l)
{
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);

	mempool_del_group(group_id);

	lua_pushboolean(l, 1);
	
	return 1;
}

/* Fetch client list int group */
int func_l_list_group_client(lua_State *l)
{
	int i, len = 0;
	
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	runner_clear_stack(l);
	int *client_ids = mempool_list_group_client(group_id, &len);
	
	lua_newtable(l);
	
	for (i = 0; i < len; i ++)
	{
		lua_pushinteger(l, i + 1);
		lua_pushinteger(l, client_ids[i]);
		lua_settable(l, -3);
	}
	
	return 1;
}

/* Group runner and room */
int func_l_get_group_runner(lua_State *l)
{
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int runner_id = mempool_get_group_runner(group_id);
	
	runner_clear_stack(l);
	lua_pushinteger(l, runner_id);

	return 1;
}

int func_l_get_group_room(lua_State *l)
{
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int room_id = mempool_get_group_room(group_id);
	
	runner_clear_stack(l);
	lua_pushinteger(l, room_id);

	return 1;
}

/* Group extends */
int func_l_set_group_extends(lua_State *l)
{
	unsigned long long data = (unsigned long long) lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int index = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	mempool_set_group_pool(group_id, index, data);
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	
	return 1;
}

int func_l_get_group_extends(lua_State *l)
{
	int index = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int group_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	unsigned long long data = mempool_get_group_pool(group_id, index);
	
	runner_clear_stack(l);
	lua_pushinteger(l, data);
	
	return 1;
}

/* Transfer coin from one to another one */
int func_l_coin_trans(lua_State *l)
{
	int actual = 0;
	static char query[1024];
	
	double tax = (double) lua_tonumber(l, -1);
	lua_remove(l, -1);

	int coin = lua_tointeger(l, -1);
	lua_remove(l, -1);

	int to_uid = lua_tointeger(l, -1);
	lua_remove(l, -1);

	int from_uid = lua_tointeger(l, -1);
	lua_remove(l, -1);

	lua_getglobal(l, "mysql_env_id");
	int env_id = lua_tointeger(l, -1);
	lua_remove(l, -1);

	actual = coin * (1.0 - tax);
	sprintf(query, "UPDATE %s SET %s = %s - %d WHERE %s = '%d');
	sprintf(query, "UPDATE %s SET %s = %s + %d WHERE %s = '%d');

	if (tax != 0)
	{
		// Tax to banker
		sprintf(query, "UPDATE %s SET %s = %s + %d WHERE %s = '%d');
	}
	
	runner_clear_stack(l);
	lua_pushinteger(l, actual);
	
	return 1;
}

/* Add event to queue */
int func_l_event_add(lua_State *l)
{
	char **p = (char **) malloc(sizeof(char *) * MAX_SEND_PARAMS);
	int ct = 0;
	int item_id = -1;
	memset(p, 0, sizeof(char *) * MAX_SEND_PARAMS);
	
	// Get table contents
	int t = lua_gettop(l);
	lua_pushnil(l);
	while (lua_next(l, t) != 0)
	{
		p[ct] = strdup(lua_tostring(l, -1));
		lua_pop(l, 1);
		ct ++;
		
		if (ct >= MAX_SEND_PARAMS)
		{
			break;
		}
	}
	
	lua_remove(l, -1);
	
	char *func = strdup(lua_tostring(l, -1));
	lua_remove(l, -1);
	
	int interval = lua_tointeger(l , -1);
	lua_remove(l, -1);
	
	int loop = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	if (client_id >= 0 && func)
	{
		item_id = queue_add_item(loop, interval, l, client_id, func, p, ct);
	}
	
	runner_clear_stack(l);
	
	if (func)
	{
		free(func);
	}
	
	lua_pushinteger(l, item_id);
	
	return 1;
}

/* Remove event from queue */
int func_l_event_del(lua_State *l)
{
	int item_id = lua_tointeger(l, -1);
	
	queue_del_item(item_id);
	
	runner_clear_stack(l);
	
	return 0;
}

/* Send data to client */
int func_l_net_send(lua_State *l)
{
	char *p[MAX_SEND_PARAMS];
	int i, ct = 0;
	unsigned int s_len = 0;
	
	// Get table contents
	int t = lua_gettop(l);
	lua_pushnil(l);
	
	while (lua_next(l, t) != 0)
	{
		if (lua_isnumber(l, -1) || lua_isstring(l, -1))
		{
			p[ct] = strdup(lua_tostring(l, -1));
			lua_pop(l, 1);
			ct ++;
		}
		
		if (ct >= MAX_SEND_PARAMS)
		{
			break;
		}
	}
	
	lua_remove(l, -1);
	
	int cmd_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	int client_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	char *stream = package_pack(p, ct, &s_len);
	put_u_int(cmd_id, stream);
	bridge_to_client(client_id, stream, s_len);
	
	// Free memory
	free(stream);
	
	for (i = 0; i < ct; i ++)
	{
		free(p[i]);
	}
	
	runner_clear_stack(l);
	lua_pushboolean(l, 1);
	
	return 1;
}

/* Close client's socket */
int func_l_net_close(lua_State *l)
{
	int client_id = lua_tointeger(l, -1);
	
	runner_clear_stack(l);
	fprintf(stderr, "Send close signal to client %d\n", client_id);
	//center_try_bridge(client_id, NULL, SGN_OFFLINE);
	
	return 0;
}

/* Set default MySQL database */
int func_l_mysql_db(lua_State *l)
{
	char *db_name = strdup(lua_tostring(l, -1));
	int env_id = runner_get_mysql_env(l);
	runner_clear_stack(l);
	
	if (env_id && strlen(db_name))
	{
		db_mysql_set_db(env_id, db_name);
		lua_pushboolean(l, 1);
	}
	
	else
	{
		lua_pushboolean(l, 0);
	}
	
	return 1;
}

/* MySQL SQL query */
int func_l_mysql_query(lua_State *l)
{
	int res = 0;
	static char query[8192];
	
	strncpy(query, lua_tostring(l, -1), 8192);
	lua_remove(l, -1);
	
	lua_getglobal(l, "mysql_env_id");
	int env_id = lua_tointeger(l, -1);
	lua_remove(l, -1);
	
	if (env_id > 0 && strlen(query))
	{
		res = db_mysql_query(env_id, query);
	}
	
	else
	{
		lua_pushnil(l);
		return 1;
	}
	
	runner_clear_stack(l);
	
	if (0 == res)
	{
		lua_pushboolean(l, 1);
	}
	
	else
	{
		lua_pushboolean(l, 0);
	}
	
	return 1;
}

/* Fetch one row from MySQL result */
int func_l_mysql_fetch_row(lua_State *l)
{
	int num_columns = 0, i;
	struct mysql_row **res;
	
	int rmode = lua_tointeger(l, -1);
	
	lua_getglobal(l, "mysql_env_id");
	int env_id = lua_tointeger(l, -1);
	
	runner_clear_stack(l);
	
	res = db_mysql_fetch_row(env_id, &num_columns);
	
	if (num_columns > 0 && res)
	{
		// Generate a new table
		lua_newtable(l);
		
		for (i = 0; i < num_columns; i ++)
		{
			if (rmode == RMODE_NUM)
			{
				lua_pushinteger(l, i + 1);
				lua_pushstring(l, res[i]->value);
				lua_settable(l, -3);
			}
			
			else if (rmode == RMODE_ASSOC)
			{
				lua_pushstring(l, res[i]->name);
				lua_pushstring(l, res[i]->value);
				lua_settable(l, -3);
			}
			
			else
			{
				// RMODE_FULL
				lua_pushstring(l, res[i]->name);
				lua_newtable(l);
			
				lua_pushstring(l, "value");
				lua_pushlstring(l, res[i]->value, res[i]->length);
				lua_settable(l, -3);
			
				lua_pushstring(l, "length");
				lua_pushinteger(l, res[i]->length);
				lua_settable(l, -3);
			
				lua_settable(l, -3);
			}
		}
		
		db_mysql_del_row(res, num_columns);
	}
	
	else
	{
		lua_pushnil(l);
	}
	
	return 1;
}

/* List all clients */
int func_l_list_client(lua_State *l)
{
	int i;
	int ct = mempool_cpool_size();
	struct client_item *cpool = mempool_cpool();
	runner_clear_stack(l);
	lua_newtable(l);

	for (i = 0; i < ct; i ++)
	{
		if (cpool[i].login_time)
		{
			lua_pushinteger(l, i + 1);
			lua_pushinteger(l, cpool[i].client_id);
			lua_settable(l, -3);
		}
	}
	
	return 1;
}

/* List all groups */
int func_l_list_group(lua_State *l)
{
	int i;
	int ct = mempool_gpool_size();
	struct group_item *gpool = mempool_gpool();
	runner_clear_stack(l);
	lua_newtable(l);

	for (i = 0; i < ct; i ++)
	{
		if (gpool[i].group_id >= 0)
		{
			lua_pushinteger(l, i + 1);
			lua_pushinteger(l, gpool[i].group_id);
			lua_settable(l, -3);
		}
	}

	return 1;
}
